package org.bridgen.runtime;

import org.bridgen.runtime.log.BadMendLeftAction;
import org.bridgen.runtime.log.BadMendRightAction;
import org.bridgen.runtime.log.GoodMendLeftAction;
import org.bridgen.runtime.log.GoodMendRightAction;
import org.bridgen.runtime.log.RecoveryLog;
import org.bridgen.runtime.model.Bridge;
import org.bridgen.runtime.model.Island;
import org.bridgen.runtime.model.LexicalNode;
import org.bridgen.runtime.model.SOF;


public class RecoveryAlgorithm {
	/**
	 * Recovers the given token list and collect a log with recovery actions
	 * @param sof The start of the list to recover
	 * @param log The recovery log
	 */
	public static void recover(SOF sof, RecoveryLog log) {
		log.clear();
		if (!sof.hasBridge()) {
			sof.buildBridge(RecoveryHelper.eof(sof), -1);
		}
		recover(sof.getBridge(), log);
	}

	/**
	 * Recovers the given token list
	 * @param sof The start on the list to recover
	 */
	public static void recover(SOF sof) {
		if (!sof.hasBridge()) {
			sof.buildBridge(RecoveryHelper.eof(sof), -1);
		}
		recover(sof.getBridge());
	}
	
	/**
	 * Builds bridges in the given token list
	 * @param sof The token list to build bridges in
	 */
	public static void buildBridges(SOF sof) {
		int startTol = 0;
//		int maxDist = nbrOfIslands(sof);
		int tol = startTol;
		while (!sof.hasBridge()) {// && distance < maxDist) {
			Island start = sof;
			boolean change = false;
			/* DEBUG  System.out.println("Starting new iteration with tolerance: " + tol); */
			while (start != null) {
				Island end = RecoveryHelper.nextUnmatchedIsland(start, tol);
				/* DEBUG System.out.println("\tTesting with start: " + start + " with end: " + end + " and tolerance: " + tol); */
				if (start.bridgeMatch(end, tol)) {
					/* DEBUG System.out.println("\t\tMatch found"); */
					start.buildBridge(end, tol);
					change = true;
					start = RecoveryHelper.nextUnmatchedStartIsland(end);
				} else {
					start = RecoveryHelper.nextUnmatchedStartIsland(start);
				}
			}
			if (!change) {
				tol++;
			} else if (tol > startTol) {
				tol = startTol;
			}
		}
	}
	
	/**
	 * Builds bridges in the given token list and documents recovery actions
	 * @param sof The token list to build bridges in
	 * @param log The recovery log to use
	 */
	public static void buildBridges(SOF sof, RecoveryLog log) {
		int startTol = 0;
		int tol = startTol;
		while (!sof.hasBridge()) {
			Island start = sof;
			boolean change = false;
			while (start != null) {
				Island end = RecoveryHelper.nextUnmatchedIsland(start, tol);
				if (start.bridgeMatch(end, tol)) {
					start.buildBridge(end, tol);
					change = true;
					start = RecoveryHelper.nextUnmatchedStartIsland(end);
				} else {
					start = RecoveryHelper.nextUnmatchedStartIsland(start);
				}
			}
			if (!change) {
				tol++;
			} else if (tol > startTol) {
				tol = startTol;
			}
		}
	}

	/**
	 * Repair unmatched islands under the given bridge
	 * @param bridge The enclosing bridge to repair content under
	 */
	public static void recover(Bridge bridge) {
		/* DEBUG System.out.println("Recovering islands under bridge: " + bridge); */
		if (bridge.isVisited()) {
			return;
		}
		bridge.setVisited(true);
		Island start = bridge.getStart();
		Island end = bridge.getEnd();
		Island island = RecoveryHelper.nextIsland(start);
		while (island != end) {
			if (!island.hasBridge()) {
				if (island.startOfBridge()) {
					mendRight(island, end);
				} else {
					mendLeft(start, island);
				}
			}
			recover(island.getBridge());
			island = RecoveryHelper.nextIsland(island.getBridge().getEnd());
		}
	}

	/**
	 * Repairs unmatched islands under the given bridge and documents recovery actions
	 * @param bridge The enclosing bridge to repair content under
	 * @param log The recovery log to use
	 */
	public static void recover(Bridge bridge, RecoveryLog log) {
		if (bridge.isVisited()) {
			return;
		}
		bridge.setVisited(true);
		Island start = bridge.getStart();
		Island end = bridge.getEnd();
		Island island = RecoveryHelper.nextIsland(start);
		while (island != end) {
			if (!island.hasBridge()) {
				if (island.startOfBridge()) {
					mendRight(island, end, log);
				} else {
					mendLeft(start, island, log);
				}
			}
			recover(island.getBridge(), log);
			island = RecoveryHelper.nextIsland(island.getBridge().getEnd());
		}
	}

	
	
	/**
	 * Mends to the left in the interval starting before the broken node 
	 * and ending before the given start node.
	 * @param start The start node
	 * @param broken The broken node
	 */
	private static void mendLeft(Island start, Island broken) {
		/* DEBUG System.out.println("Mending to the left, start: " + start + ", broken: " + broken); */
		LexicalNode node = broken.getPrevious();
		while (node != start) {
			if (broken.possibleConstructionSite(node)) {
				/*
				Island island = broken.constructFakeIsland(node, false);
				broken.insertFakeIsland(island, node);
				broken.buildBridge(island, -1);
				*/
				return;
			} else if (node instanceof Island && ((Island)node).hasBridge()) {
				Bridge bridge = ((Island)node).getBridge();
				node = bridge.getStart().getPrevious();
			} else { 
				node = node.getPrevious();
			}
		}
		Island island = broken.constructFakeIsland(start, true);
		RecoveryHelper.insertAfter(island, start);
		broken.buildBridge(island, -1);
	}
	
	/**
	 * Mends to the left in the interval starting before the broken node 
	 * and ending before the given start node, while documenting actions in
	 * the given recovery log.
	 * @param start The start node
	 * @param broken The broken node
	 * @param log The recovery log to use
	 */
	private static void mendLeft(Island start, Island broken, RecoveryLog log) {
		LexicalNode node = broken.getPrevious();
		while (node != start) {
			if (broken.possibleConstructionSite(node)) {
				Island newIsland = broken.getBridge().getStart();
				log.addAction(new GoodMendLeftAction(newIsland));
				/*
				Island island = broken.constructFakeIsland(node, false);
				broken.insertFakeIsland(island, node);
				broken.buildBridge(island, -1);
				// Log: Found construction site to the left in recovery log
				log.addAction(new GoodMendLeftAction(island));
				*/
				return;
			} else if (node instanceof Island && ((Island)node).hasBridge()) {
				Bridge bridge = ((Island)node).getBridge();
				node = bridge.getStart().getPrevious();
			} else { 
				node = node.getPrevious();
			}
		}

		Island island = broken.constructFakeIsland(start, true);
		RecoveryHelper.insertAfter(island, start);
		broken.buildBridge(island, -1);
		// Log: Didn't find a construction site in the recovery log
		log.addAction(new BadMendLeftAction(island));
	}


	/**
	 * Mends to the right in the interval starting after the broken node
	 * and ending before the given end node.
	 * @param broken The broken node
	 * @param end The end node
	 */
	private static void mendRight(Island broken, Island end) {
		/* DEBUG System.out.println("Mending to the right, broken: " + broken + ", end: " + end); */
		LexicalNode node  = broken.getNext();
		while (node != end) {
			/* DEBUG System.out.println("mendright: node = " + node); */
			if (broken.possibleConstructionSite(node)) {
				/*
				Island island = broken.constructFakeIsland(node, false);
				broken.insertFakeIsland(island, node);
				broken.buildBridge(island, -1);
				*/
				return;
			} else if (node instanceof Island && ((Island)node).hasBridge()) {
				Bridge bridge = ((Island)node).getBridge();
				node = bridge.getEnd().getNext();
			} else { 
				node = node.getNext();
			}
		}
	
		Island island = broken.constructFakeIsland(end, true);
		RecoveryHelper.insertBefore(island, end);
		broken.buildBridge(island, -1);	
	}
	
	/**
	 * Mends to the right in the interval starting after the broken node
	 * and ending before the given end node, while documenting actions in
	 * a given recovery log.
	 * @param broken The broken node
	 * @param end The end node
	 * @param log The recovery log
	 */
	private static void mendRight(Island broken, Island end, RecoveryLog log) {
		LexicalNode node  = broken.getNext();
		while (node != end) {
			if (broken.possibleConstructionSite(node)) {
				Island newIsland = broken.getBridge().getEnd();
				log.addAction(new GoodMendRightAction(newIsland));
				/*
				Island island = broken.constructFakeIsland(node, false);
				broken.insertFakeIsland(island, node);
				broken.buildBridge(island, -1);
				// Log: Found construction site to the right in the recovery log
				log.addAction(new GoodMendRightAction(island));
				*/
				return;
			} else if (node instanceof Island && ((Island)node).hasBridge()) {
				Bridge bridge = ((Island)node).getBridge();
				node = bridge.getEnd().getNext();
			} else { 
				node = node.getNext();
			}
		}
		Island island = broken.constructFakeIsland(end, true);
		RecoveryHelper.insertBefore(island, end);
		broken.buildBridge(island, -1);	
		// Log: Add didn't find a construction site to the right in the recovery log
		log.addAction(new BadMendRightAction(island));
	}
}
